package xutil

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"strings"

	//"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"time"

	"gitee.com/wosylf/ltool"
	"gitee.com/wosylf/ltool/pkg/errors"
)

const (
	Host     = "https://openapi.esign.cn"
	TestHost = "https://smlopenapi.esign.cn"
	V合同上传接口  = "/v3/files/file-upload-url"
	V发起签署    = "/v3/sign-flow/create-by-file"

	V签署流程_结束 = "SIGN_FLOW_COMPLETE"
)

type Esign struct {
	ProjectId    string //易签宝id
	ProjectScert string //易签宝密钥
	CompanyName  string `gorm:"type:varchar(24);comment:'e签宝应用公司名'" json:"company_name,omitempty"`
	CompanyNum   string `gorm:"type:varchar(24);comment:'e签宝应用公司号'" json:"company_num,omitempty"`
	NotifyUrl    string
	RedirectUrl  string
	Orgid        string `gorm:"type:varchar(46);comment:'e签宝生成的公司id'" json:"orgid,omitempty"`
	Istest       bool
}

func F新建易签宝(id, scert, company_name, company_num, orgid, notifyurl, RedirectUrl string) *Esign {
	return &Esign{id, scert, company_name, company_num, notifyurl, RedirectUrl, orgid, true}
}

func (e *Esign) F获取签署链接(signFlowId, psnacc string) (inte *S签署链接返回, err error) {
	url := "/v3/sign-flow/" + signFlowId + "/sign-url"
	bm := make(ltool.BodyMap)
	bm.Set("signFlowId", signFlowId).
		SetBodyMap("operator", func(b ltool.BodyMap) {
			b.Set("psnAccount", psnacc)
		})

	inte = new(S签署链接返回)
	_, err = e.SendCommHttp(url, bm.JsonBody(), "POST", inte)
	if err != nil {
		return
	}
	if inte.Code != 0 && inte.Message != "账号已存在" {
		err = errors.New(inte.Message)
	}
	return
}

func (e *Esign) F通过文件发起签署(fileid, phone, name, idcard string) (inte *S签署信息返回, err error) {
	bd := &S发起签署{
		SignFlowConfig: &SignFlowConfig{
			SignFlowTitle:  "租赁合同",
			AutoFinish:     true,
			NotifyUrl:      e.NotifyUrl,
			RedirectConfig: RedirectConfig{RedirectUrl: ""},
		},
	}
	bd.Docs = append(bd.Docs, &Doc{
		FileId:   fileid,
		FileName: "租赁合同",
	})
	//先添加企业
	bd.Signers = append(bd.Signers, &Signers{
		SignerType: 1,
		SignConfig: &SignConfig{
			SignOrder: 1,
		},
		SignFields: []Signfield{
			{FileId: fileid,
				NormalSignFieldConfig: NormalSignFieldConfig{
					AutoSign:       true,
					SignFieldStyle: 1,
					//AssignedSealId: "4de9c83a-ec02-42ea-8c9a-65a424ccaaf4",
					SignFieldPosition: SignFieldPosition{
						PositionPage: 1,
						PositionX:    100,
						PositionY:    100,
					},
				},
			},
		},
	})
	//添加个人
	bd.Signers = append(bd.Signers, &Signers{
		SignerType: 0,
		SignConfig: &SignConfig{
			SignOrder: 2,
		},
		PsnSignerInfo: &PsnSignerInfo{
			PsnAccount: phone,
			PsnInfo: PsnInfo{
				PsnName:      name,
				PsnIDCardNum: idcard,
			},
		},
		SignFields: []Signfield{
			{FileId: fileid,
				NormalSignFieldConfig: NormalSignFieldConfig{
					SignFieldStyle: 1,
					SignFieldPosition: SignFieldPosition{
						PositionPage: 1,
						PositionX:    200,
						PositionY:    100,
					},
				},
			},
		},
	})

	//发起签署请求
	inte = new(S签署信息返回)
	_, err = e.SendCommHttp(V发起签署, bd, "POST", inte)
	if err != nil {
		return
	}
	if inte.Code != 0 {
		err = errors.New(inte.Message)
	}
	return
}

// 创建个人账号
func (e *Esign) F创建个人账号(idc, name, phone, email string) (inte *S创建个人信息返回, err error) {
	bm := make(ltool.BodyMap)
	bm.Set("email", email).
		Set("idNumber", idc).
		Set("thirdPartyUserId", idc).
		Set("mobile", phone).
		Set("name", name)

	apiUrl := "/v1/accounts/createByThirdPartyUserId"
	inte = new(S创建个人信息返回)
	_, err = e.SendCommHttp(apiUrl, bm.JsonBody(), "POST", inte)
	if err != nil {
		return
	}
	if inte.Code != 0 && inte.Message != "账号已存在" {
		err = errors.New(inte.Message)
	}
	return
}
func (e *Esign) F查询企业认证(orgName, orgIDCardNum string) (err error) {

	bm := make(ltool.BodyMap)
	bm.Set("orgName", orgName).
		Set("orgIDCardNum", orgIDCardNum).
		Set("orgIDCardType", "CRED_ORG_USCC")

	apiUrl := "/v3/organizations/identity-info"
	//inte = new(S创建个人信息返回)
	by, err := e.SendCommHttp(apiUrl, bm.JsonBody(), "POST", nil)
	if err != nil {
		return
	}
	fmt.Println("bu", string(by))
	return
}
func (e *Esign) F查询签署情况(flowid string) (resp *S查询签署返回, err error) {
	apiUrl := "/v3/sign-flow/" + flowid + "/detail"
	//inte = new(S创建个人信息返回)
	resp = new(S查询签署返回)
	_, err = e.SendCommHttp(apiUrl, "", "GET", resp)
	if err != nil {
		return
	}
	//fmt.Println("bu", string(by))
	return
}

func (e *Esign) F创建企业认证(orgName, orgIDCardNum string) (err error) {

	bm := make(ltool.BodyMap)
	bm.SetBodyMap("orgAuthConfig", func(b ltool.BodyMap) {
		b.Set("orgName", orgName).SetBodyMap("orgInfo", func(b ltool.BodyMap) {
			b.Set("orgIDCardNum", orgIDCardNum).Set("orgIDCardType", "CRED_ORG_USCC")
		})
	})

	apiUrl := "/v3/org-auth-url"
	//inte = new(S创建个人信息返回)
	by, err := e.SendCommHttp(apiUrl, bm.JsonBody(), "POST", nil)
	if err != nil {
		return
	}
	fmt.Println("bu", string(by))
	return
}

func (e *Esign) F获取文件位置(fileid string, keyword []string) (resp *S查找关键词返回, err error) {
	文件地址 := "/v3/files/" + fileid + "/keyword-positions"
	bm := make(ltool.BodyMap)
	bm.Set("keywords", keyword)
	resp = new(S查找关键词返回)
	_, err = e.SendCommHttp(文件地址, bm.JsonBody(), "POST", resp)
	//fmt.Println("errby", string(by))
	if err != nil {
		err = errors.WithStack(err)
	}
	return
}

// 获取文件上传地址
func (e *Esign) F文件上传地址获取(文件地址, filename string) (inte *S文件上传返回, err error) {
	contentMd5, size := CountFileMd5(文件地址)
	filetype := "application/pdf"
	bm := make(ltool.BodyMap)
	bm.Set("contentMd5", contentMd5).
		Set("contentType", filetype).
		Set("convert2Pdf", !strings.Contains(文件地址, ".pdf")).
		Set("fileSize", size).
		Set("fileName", 文件地址)
	inte = new(S文件上传返回)
	_, err = e.SendCommHttp(V合同上传接口, bm.JsonBody(), "POST", inte)
	if err != nil {
		return
	}
	if inte.Code != 0 {
		err = errors.New(inte.Message)
		return
	}
	resp, err := F上传文件(inte.Data.FileUploadUrl, 文件地址, contentMd5, filetype)
	if err != nil {
		return
	}
	if resp.ErrCode != 0 {
		err = errors.New(resp.Msg)
	}
	fmt.Println("上传成功", resp)
	inte.Data.FileUploadUrl = ""
	return
}

func (e *Esign) SendCommHttp(apiUrl string, dataJsonStr interface{}, method string, inter interface{}) (by []byte, err error) {
	httpUrl := Host + apiUrl
	if e.Istest {
		httpUrl = TestHost + apiUrl
	}
	str, ok := dataJsonStr.(string)
	if ok {
		fmt.Println("转换为字符串成功:", str)
	} else {
		bs, err := json.Marshal(dataJsonStr)
		if err != nil {
			return nil, err
		}
		str = string(bs)
		fmt.Println("===>易签宝请求数据", str, httpUrl)
	}
	md5Str := DohashMd5(str)
	message := AppendSignDataString(method, "*/*", md5Str, "application/json; charset=UTF-8", "", "", apiUrl)
	reqSignature := DoSignatureBase64(message, e.ProjectScert)
	// 初始化接口返回值
	by, err = F发送请求(httpUrl, str, method, e.F获取头部参数(md5Str, reqSignature))
	if err != nil {
		err = errors.WithStack(err)
	}
	if inter != nil {
		err = errors.WithStack(json.Unmarshal(by, inter))
	}
	return
}

// sha256摘要签名
func DoSignatureBase64(message string, secret string) string {
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(message))
	buf := h.Sum(nil)
	return base64.StdEncoding.EncodeToString(buf)
}

// 拼接请求参数
func AppendSignDataString(method string, accept string, contentMD5 string, contentType string, date string, headers string, url string) string {
	var buffer bytes.Buffer
	buffer.WriteString(method)
	buffer.WriteString("\n")
	buffer.WriteString(accept)
	buffer.WriteString("\n")
	buffer.WriteString(contentMD5)
	buffer.WriteString("\n")
	buffer.WriteString(contentType)
	buffer.WriteString("\n")
	buffer.WriteString(date)
	buffer.WriteString("\n")
	if len(headers) == 0 {
		buffer.WriteString(headers)
		buffer.WriteString(url)
	} else {
		buffer.WriteString(headers)
		buffer.WriteString("\n")
		buffer.WriteString(url)
	}
	return buffer.String()
}

func (e *Esign) F获取头部参数(contentMD5 string, reqSignature string) (header map[string]string) {
	headers := map[string]string{}
	headers["X-Tsign-Open-App-Id"] = e.ProjectId
	headers["X-Tsign-Open-Ca-Timestamp"] = strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
	headers["Accept"] = "*/*"
	headers["X-Tsign-Open-Ca-Signature"] = reqSignature
	headers["Content-MD5"] = contentMD5
	headers["Content-Type"] = "application/json; charset=UTF-8"
	headers["X-Tsign-Open-Auth-Mode"] = "Signature"
	return headers
}

// 文件上传
func F上传文件(uploadUrl string, filePath string, contentMD5 string, contentType string) (resp *S文件上传结果, err error) {
	//创建一个缓冲区对象,后面的要上传的body都存在这个缓冲区里
	bodyBuf := &bytes.Buffer{}
	//要上传的文件
	//创建第一个需要上传的文件,filepath.Base获取文件的名称
	//打开文件
	fd1, _ := os.Open(filePath)
	defer fd1.Close()
	//把第一个文件流写入到缓冲区里去
	_, _ = io.Copy(bodyBuf, fd1)
	//获取请求Content-Type类型,后面有用
	//contentType := bodyWriter.FormDataContentType()
	//创建一个http客户端请求对象
	client := &http.Client{}
	//创建一个post请求
	req, _ := http.NewRequest("PUT", uploadUrl, nil)
	//设置请求头
	req.Header.Set("Content-MD5", contentMD5)
	//这里的Content-Type值就是上面contentType的值
	req.Header.Set("Content-Type", contentType)
	//转换类型
	req.Body = io.NopCloser(bodyBuf)
	//发送数据
	data, _ := client.Do(req)
	//读取请求返回的数据
	bytes, _ := io.ReadAll(data.Body)
	defer data.Body.Close()
	resp = new(S文件上传结果)
	if err = json.Unmarshal(bytes, resp); err != nil {
		err = errors.WithStack(err)
	}
	//返回数据
	return
}

func F发送请求(apiUrl string, data string, method string, headers map[string]string) (by []byte, err error) {
	// API接口返回值by
	url := apiUrl
	var jsonStr = []byte(data)
	var req *http.Request
	if method == "GET" || method == "DELETE" {
		req, err = http.NewRequest(method, url, nil)
	} else {
		req, err = http.NewRequest(method, url, bytes.NewBuffer(jsonStr))
	}
	if err != nil {
		return
	}
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	client := &http.Client{}
	resp, err := client.Do(req)

	if err != nil {
		return
	}
	by, err = io.ReadAll(resp.Body)
	return

}
